﻿//------------------------------------------------------------------------------
// Copyright © FRA & FV 2014
// All rights reserved
//------------------------------------------------------------------------------
// Binary Serialization SDK
//
// SVN revision information:
//   $Revision: 97 $
//------------------------------------------------------------------------------
using System;
using System.Text;

namespace FRAFV.Binary.Serialization
{
	/// <summary>Represents text as a series of encoded characters in binary data.</summary>
	[System.Diagnostics.DebuggerDisplay("{text}[{Data.Length}]")]
	public class PadString : ICloneable
	{
		private string text;

		private PadString() { }

		/// <summary>Default binary data encoding for <see cref="T:FRAFV.Binary.Serialization.PadString" /> instance.</summary>
		public static Encoding DefaultEncoding { get { return Encoding.Default; } }

		/// <summary>Get the binary data.</summary>
		/// <returns>Binary data.</returns>
		public byte[] Data { get; private set; }

		/// <summary>Gets the instance string from a null-terminated array of encoded characters in binary data.</summary>
		/// <returns>The instance string.</returns>
		/// <exception cref="T:System.ArgumentNullException">
		///   The specified string value is null.</exception>
		/// <exception cref="T:System.ArgumentException">
		///   <see cref="P:FRAFV.Binary.Serialization.PadString.Data"/> does not have enough capacity to accommodate the resulting bytes. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public string Text
		{
			get
			{
				if (text == null)
					text = PadData();
				return text;
			}
			set
			{
				text = null;
				FillData(value);
			}
		}

		/// <summary>Gets the binary data encoding.</summary>
		/// <returns>Bynary data encoding.</returns>
		public virtual Encoding DataEncoding { get; private set; }

		/// <summary>Gets the <see cref="T:System.Char" /> object at a specified position in the current
		///   <see cref="T:FRAFV.Binary.Serialization.PadString" /> object.</summary>
		/// <returns>The <see cref="T:System.Char" /> at position <paramref name="index" />.</returns>
		/// <param name="index">A position in the current string. </param>
		/// <exception cref="T:System.IndexOutOfRangeException">
		///   <paramref name="index" /> is greater than or equal to the length of this object or less than zero. </exception>
		public char this[int index] { get { return Text[index]; } }

		/// <summary>Gets the number of characters in the current
		///   <see cref="T:FRAFV.Binary.Serialization.PadString" /> object.</summary>
		/// <returns>The number of characters in the current string.</returns>
		public int Length { get { return Text.Length; } }

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.PadString" /> class
		///   to the value indicated by a specified pointer to an binary data of encoded characters.</summary>
		/// <param name="data">Bynary data of encoded characters.</param>
		/// <param name="encoding">Bynary data encoding.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="data" /> is null. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public PadString(byte[] data, Encoding encoding)
		{
			if (data == null)
				throw new ArgumentNullException("data");
			this.Data = data;
			this.DataEncoding = encoding ?? DefaultEncoding;
			this.text = PadData();
		}

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.PadString" /> class
		///   by the string value to fill a binary data space by encoded characters.</summary>
		/// <param name="text">A string value.</param>
		/// <param name="space">Binary data space length in bytes.</param>
		/// <param name="encoding">Bynary data encoding.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="text" /> is null. </exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">
		///   <paramref name="space" /> is less than zero.</exception>
		/// <exception cref="T:System.ArgumentException">
		///   The <paramref name="space" /> isn't enough to accommodate the resulting bytes. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public PadString(string text, int space, Encoding encoding)
		{
			if (text == null)
				throw new ArgumentNullException("text");
			if (space < 0)
				throw new ArgumentOutOfRangeException("space");
			this.DataEncoding = encoding ?? DefaultEncoding;
			this.Data = new byte[space];
			FillData(text);
			this.text = PadData();

		}

		/// <summary>Returns this instance <see cref="T:FRAFV.Binary.Serialization.PadString" /> string from
		///   a null-terminated array of encoded characters in binary data.</summary>
		/// <returns>The current instance string.</returns>
		public override string ToString()
		{
			return Text;
		}

		private string PadData()
		{
			var decoder = DataEncoding.GetDecoder();
			var sb = new StringBuilder();
			var buf = new char[1];
			int size;
			switch (DataEncoding.CodePage)
			{
				case 1200:
				case 1201:
					size = 2;
					break;
				case 12000:
				case 12001:
					size = 4;
					break;
				default:
					size = 1;
					break;
			}
			for (int k = 0; k <= Data.Length - size; k += size)
			{
				int read = decoder.GetChars(Data, k, size, buf, 0);
				if (read == 0) continue;
				char c = buf[0];
				if (c == '\0') break;
				sb.Append(c);
			}
			return sb.ToString();
		}

		private void FillData(string value)
		{
			int read = DataEncoding.GetBytes(value, 0, value.Length, Data, 0);
			if (read < Data.Length)
				Array.Clear(Data, read, Data.Length - read);
		}

		#region Interfaces implementation

		/// <summary>Creates a new object that is a copy of the current instance.</summary>
		/// <returns>A new object that is a copy of this instance.</returns>
		public object Clone()
		{
			return new PadString
			{
				Data = (byte[])Data.Clone(),
				DataEncoding = DataEncoding,
				text = text
			};
		}

		/// <summary>Determines whether this instance string and a specified object, which must also be
		///   a <see cref="T:System.String" /> object or a <see cref="T:FRAFV.Binary.Serialization.PadString" /> object,
		///   have the same string.</summary>
		/// <returns>true if <paramref name="obj" /> is a <see cref="T:System.String" /> or a <see cref="T:FRAFV.Binary.Serialization.PadString" />
		///   and its string is the same as this instance string; otherwise, false.</returns>
		/// <param name="obj">The object string to compare to this instance. </param>
		public override bool Equals(object obj)
		{
			string str = obj as string;
			PadString pad = obj as PadString;
			return (str != null && Text.Equals(str)) ||
				(pad != null && Text.Equals(pad.Text) && Data.Length == pad.Data.Length);
		}

		/// <summary>Returns the hash code for this instance string.</summary>
		/// <returns>A 32-bit signed integer hash code.</returns>
		public override int GetHashCode()
		{
			return Text.GetHashCode() ^ Data.Length.GetHashCode();
		}

		#endregion

		#region Cast operators

		/// <summary>Converts a <see cref="T:FRAFV.Binary.Serialization.PadString" /> object to a string value.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A string value that represents the <see cref="T:FRAFV.Binary.Serialization.PadString" /> string.</returns>
		public static implicit operator string(PadString value)
		{
			return value == null ? null : value.text;
		}

		/// <summary>Converts a <see cref="T:FRAFV.Binary.Serialization.PadString" /> object to a binary data.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A binary data that represents the <see cref="T:FRAFV.Binary.Serialization.PadString" /> data.</returns>
		public static implicit operator byte[](PadString value)
		{
			return value.Data;
		}

		/// <summary>Converts a binary data to a <see cref="T:FRAFV.Binary.Serialization.PadString" /> object.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A <see cref="T:FRAFV.Binary.Serialization.PadString" /> object that contains the binary data.</returns>
		public static implicit operator PadString(byte[] value)
		{
			return new PadString(value, DefaultEncoding);
		}

		#endregion
	}

	/// <summary>Represents text as a series of UTF-8 characters in binary data.</summary>
	public sealed class UTF8PadString : PadString
	{
		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF8PadString" /> class
		///   to the value indicated by a specified pointer to an bynary data of UTF-8 characters.</summary>
		/// <param name="data">Bynary data of UTF-8 characters.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="data" /> is null. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF8PadString(byte[] data) : base(data, Encoding.UTF8) { }

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF8PadString" /> class
		///   by the string value to fill a binary data space by UTF-8 characters.</summary>
		/// <param name="text">A string value.</param>
		/// <param name="space">Binary data space length in bytes.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="text" /> is null. </exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">
		///   <paramref name="space" /> is less than zero.</exception>
		/// <exception cref="T:System.ArgumentException">
		///   The <paramref name="space" /> isn't enough to accommodate the resulting bytes. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF8PadString(string text, int space) : base(text, space, Encoding.UTF8) { }

		/// <summary>Gets the binary data encoding.</summary>
		/// <returns>Bynary data encoding.</returns>
		public override Encoding DataEncoding { get { return Encoding.UTF8; } }

		#region Cast operators

		/// <summary>Converts a binary data to a <see cref="T:FRAFV.Binary.Serialization.UTF8PadString" /> object.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A <see cref="T:FRAFV.Binary.Serialization.UTF8PadString" /> object that contains the binary data.</returns>
		public static implicit operator UTF8PadString(byte[] value)
		{
			return new UTF8PadString(value);
		}

		#endregion
	}

	/// <summary>Represents text as a series of UTF-16 characters in little-endian binary data.</summary>
	public sealed class UTF16LEPadString : PadString
	{
		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF16LEPadString" /> class
		///   to the value indicated by a specified pointer to an little-endian bynary data of UTF-16 characters.</summary>
		/// <param name="data">Bynary data of UTF-16 characters.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="data" /> is null. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF16LEPadString(byte[] data) : base(data, Encoding.Unicode) { }

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF16LEPadString" /> class
		///   by the string value to fill an little-endian bynary data space by UTF-16 characters.</summary>
		/// <param name="text">A string value.</param>
		/// <param name="space">Binary data space length in bytes.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="text" /> is null. </exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">
		///   <paramref name="space" /> is less than zero.</exception>
		/// <exception cref="T:System.ArgumentException">
		///   The <paramref name="space" /> isn't enough to accommodate the resulting bytes. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF16LEPadString(string text, int space) : base(text, space, Encoding.Unicode) { }

		/// <summary>Gets the binary data encoding.</summary>
		/// <returns>Bynary data encoding.</returns>
		public override Encoding DataEncoding { get { return Encoding.Unicode; } }

		#region Cast operators

		/// <summary>Converts a binary data to a <see cref="T:FRAFV.Binary.Serialization.UTF16LEPadString" /> object.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A <see cref="T:FRAFV.Binary.Serialization.UTF16LEPadString" /> object that contains the binary data.</returns>
		public static implicit operator UTF16LEPadString(byte[] value)
		{
			return new UTF16LEPadString(value);
		}

		#endregion
	}

	/// <summary>Represents text as a series of UTF-16 characters in big-endian binary data.</summary>
	public sealed class UTF16BEPadString : PadString
	{
		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF16BEPadString" /> class
		///   to the value indicated by a specified pointer to an big-endian bynary data of UTF-16 characters.</summary>
		/// <param name="data">Bynary data of UTF-16 characters.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="data" /> is null. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF16BEPadString(byte[] data) : base(data, Encoding.BigEndianUnicode) { }

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF16BEPadString" /> class
		///   by the string value to fill an big-endian binary data space by UTF-16 characters.</summary>
		/// <param name="text">A string value.</param>
		/// <param name="space">Binary data space length in bytes.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="text" /> is null. </exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">
		///   <paramref name="space" /> is less than zero.</exception>
		/// <exception cref="T:System.ArgumentException">
		///   The <paramref name="space" /> isn't enough to accommodate the resulting bytes. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF16BEPadString(string text, int space) : base(text, space, Encoding.BigEndianUnicode) { }

		/// <summary>Gets the binary data encoding.</summary>
		/// <returns>Bynary data encoding.</returns>
		public override Encoding DataEncoding { get { return Encoding.BigEndianUnicode; } }

		#region Cast operators

		/// <summary>Converts a binary data to a <see cref="T:FRAFV.Binary.Serialization.UTF16BEPadString" /> object.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A <see cref="T:FRAFV.Binary.Serialization.UTF16BEPadString" /> object that contains the binary data.</returns>
		public static implicit operator UTF16BEPadString(byte[] value)
		{
			return new UTF16BEPadString(value);
		}

		#endregion
	}

	/// <summary>Represents text as a series of UTF-32 characters in little-endian binary data.</summary>
	public sealed class UTF32LEPadString : PadString
	{
		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF32LEPadString" /> class
		///   to the value indicated by a specified pointer to an little-endian bynary data of UTF-32 characters.</summary>
		/// <param name="data">Bynary data of UTF-32 characters.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="data" /> is null. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF32LEPadString(byte[] data) : base(data, Encoding.UTF32) { }

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF32LEPadString" /> class
		///   by the string value to fill an little-endian binary data space by UTF-32 characters.</summary>
		/// <param name="text">A string value.</param>
		/// <param name="space">Binary data space length in bytes.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="text" /> is null. </exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">
		///   <paramref name="space" /> is less than zero.</exception>
		/// <exception cref="T:System.ArgumentException">
		///   The <paramref name="space" /> isn't enough to accommodate the resulting bytes. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF32LEPadString(string text, int space) : base(text, space, Encoding.UTF32) { }

		/// <summary>Gets the binary data encoding.</summary>
		/// <returns>Bynary data encoding.</returns>
		public override Encoding DataEncoding { get { return Encoding.UTF32; } }

		#region Cast operators

		/// <summary>Converts a binary data to a <see cref="T:FRAFV.Binary.Serialization.UTF32LEPadString" /> object.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A <see cref="T:FRAFV.Binary.Serialization.UTF32LEPadString" /> object that contains the binary data.</returns>
		public static implicit operator UTF32LEPadString(byte[] value)
		{
			return new UTF32LEPadString(value);
		}

		#endregion
	}

	/// <summary>Represents text as a series of UTF-32 characters in big-endian binary data.</summary>
	public sealed class UTF32BEPadString : PadString
	{
		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF32BEPadString" /> class
		///   to the value indicated by a specified pointer to an big-endian bynary data of UTF-32 characters.</summary>
		/// <param name="data">Bynary data of UTF-32 characters.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="data" /> is null. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF32BEPadString(byte[] data) : base(data, new UTF32Encoding(true, true)) { }

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.UTF32BEPadString" /> class
		///   by the string value to fill an big-endian binary data space by UTF-32 characters.</summary>
		/// <param name="text">A string value.</param>
		/// <param name="space">Binary data space length in bytes.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="text" /> is null. </exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">
		///   <paramref name="space" /> is less than zero.</exception>
		/// <exception cref="T:System.ArgumentException">
		///   The <paramref name="space" /> isn't enough to accommodate the resulting bytes. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public UTF32BEPadString(string text, int space) : base(text, space, new UTF32Encoding(true, true)) { }

		#region Cast operators

		/// <summary>Converts a binary data to a <see cref="T:FRAFV.Binary.Serialization.UTF32BEPadString" /> object.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A <see cref="T:FRAFV.Binary.Serialization.UTF32BEPadString" /> object that contains the binary data.</returns>
		public static implicit operator UTF32BEPadString(byte[] value)
		{
			return new UTF32BEPadString(value);
		}

		#endregion
	}

	/// <summary>Represents text as a series of Macintosh encoded characters in binary data.</summary>
	public sealed class MacPadString : PadString
	{
		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.MacPadString" /> class
		///   to the value indicated by a specified pointer to an bynary data of Macintosh encoded characters.</summary>
		/// <param name="data">Bynary data of Macintosh encoded characters.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="data" /> is null. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public MacPadString(byte[] data) : base(data, Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentUICulture.TextInfo.MacCodePage)) { }

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.MacPadString" /> class
		///   by the string value to fill a binary data space by Macintosh characters.</summary>
		/// <param name="text">A string value.</param>
		/// <param name="space">Binary data space length in bytes.</param>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="text" /> is null. </exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">
		///   <paramref name="space" /> is less than zero.</exception>
		/// <exception cref="T:System.ArgumentException">
		///   The <paramref name="space" /> isn't enough to accommodate the resulting bytes. </exception>
		/// <exception cref="T:System.Text.EncoderFallbackException">
		///   A fallback occurred.</exception>
		public MacPadString(string text, int space) : base(text, space, Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentUICulture.TextInfo.MacCodePage)) { }

		#region Cast operators

		/// <summary>Converts a binary data to a <see cref="T:FRAFV.Binary.Serialization.MacPadString" /> object.</summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>A <see cref="T:FRAFV.Binary.Serialization.MacPadString" /> object that contains the binary data.</returns>
		public static implicit operator MacPadString(byte[] value)
		{
			return new MacPadString(value);
		}

		#endregion
	}
}
